ssh exit status 255 is a connection problem
authorJoey Hess <joeyh@joeyh.name>
Fri, 3 Jan 2025 18:33:24 +0000 (14:33 -0400)
committerJoey Hess <joeyh@joeyh.name>
Fri, 3 Jan 2025 18:46:16 +0000 (14:46 -0400)
Previously, when the git config was unable to be read from a ssh remote,
it would try to git fetch from it to determine if the remote was
otherwise accessible. That was unnessary work, since exit status 255
indicates a connection problem.

As well as avoiding the extra work of the fetch, this also improves
things when a ssh remote cannot be connected to due to a problem with
the git-annex ssh control socket. In that situation, ssh will also exit 255.
Before, the git fetch was tried in that situation, and would succeed, since
it does not use the git-annex ssh control socket. git-annex would conclude
that git-annex-shell was not installed on the remote, which could be wrong.

I suppose it also used to be possible for the user to need to enter a
ssh password on each connection to the remote. If they entered the wrong
password for the git-annex-shell call, but then the right password for
the git fetch, it would also incorrectly set annex-ignore, and that
situation is also now fixed.

CHANGELOG
Git/Config.hs
Remote/GCrypt.hs
Remote/Git.hs
doc/forum/Control_socket_connect__40__..__47__.git__47__annex__47__ssh__47__server.lo/comment_1_f530349d80ca5f43e9b2304f63e4b960._comment [new file with mode: 0644]

index 7df1684639de33d69d9b7e0084e9b7b2dd6f3116..d0c23b2b192cd2afda3a2a252d343dbf40b45103 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,10 @@
+git-annex (10.20250103) UNRELEASED; urgency=medium
+
+  * Improve handing of ssh connection problems during 
+    remote annex.uuid discovery.
+
+ -- Joey Hess <id@joeyh.name>  Fri, 03 Jan 2025 14:30:38 -0400
+
 git-annex (10.20250102) upstream; urgency=medium
 
   * Added config `url.<base>.annexInsteadOf` corresponding to git's
index 82edf690026684568d3a4d37f1fb4863592fcf92..b6fd77b24934aa4a3ca52117502be9563165690c 100644 (file)
@@ -255,9 +255,9 @@ coreBare = "core.bare"
 
 {- Runs a command to get the configuration of a repo,
  - and returns a repo populated with the configuration, as well as the raw
- - output and the standard error of the command. -}
-fromPipe :: Repo -> String -> [CommandParam] -> ConfigStyle -> IO (Either SomeException (Repo, S.ByteString, String))
-fromPipe r cmd params st = tryNonAsync $ withCreateProcess p go
+ - output and the exit status and standard error of the command. -}
+fromPipe :: Repo -> String -> [CommandParam] -> ConfigStyle -> IO (Repo, S.ByteString, ExitCode, String)
+fromPipe r cmd params st = withCreateProcess p go
   where
        p = (proc cmd $ toCommand params)
                { std_out = CreatePipe
@@ -267,9 +267,13 @@ fromPipe r cmd params st = tryNonAsync $ withCreateProcess p go
                withAsync (getstderr pid herr []) $ \errreader -> do
                        val <- S.hGetContents hout
                        err <- wait errreader
-                       forceSuccessProcess p pid
-                       r' <- store val st r
-                       return (r', val, err)
+                       exitcode <- waitForProcess pid
+                       case exitcode of
+                               ExitSuccess -> do
+                                       r' <- store val st r
+                                       return (r', val, exitcode, err)
+                               ExitFailure _ ->
+                                       return (r, val, exitcode, err)
        go _ _ _ _ = error "internal"
 
        getstderr pid herr c = hGetLineUntilExitOrEOF pid herr >>= \case
@@ -278,7 +282,7 @@ fromPipe r cmd params st = tryNonAsync $ withCreateProcess p go
 
 {- Reads git config from a specified file and returns the repo populated
  - with the configuration. -}
-fromFile :: Repo -> FilePath -> IO (Either SomeException (Repo, S.ByteString, String))
+fromFile :: Repo -> FilePath -> IO (Repo, S.ByteString, ExitCode, String)
 fromFile r f = fromPipe r "git"
        [ Param "config"
        , Param "--file"
index cb549b7994c8a842219d40f1cf161228bbe1b1c7..8a3852c6b102b334b9e37ddfd8fa24d225dee469 100644 (file)
@@ -22,7 +22,6 @@ import qualified Data.Map as M
 import qualified Data.ByteString as S
 import qualified Data.ByteString.Lazy as L
 import qualified System.FilePath.ByteString as P
-import Control.Exception
 import Data.Default
 
 import Annex.Common
@@ -59,7 +58,6 @@ import Utility.Tmp
 import Logs.Remote
 import Utility.Gpg
 import Utility.SshHost
-import Utility.Tuple
 import Utility.Directory.Create
 import Messages.Progress
 import Types.ProposedAccepted
@@ -508,16 +506,25 @@ getGCryptId :: Bool -> Git.Repo -> RemoteGitConfig -> Annex (Maybe Git.GCrypt.GC
 getGCryptId fast r gc
        | Git.repoIsLocal r || Git.repoIsLocalUnknown r = extract <$>
                liftIO (catchMaybeIO $ Git.Config.read r)
-       | not fast = extract . liftM fst3 <$> getM (eitherToMaybe <$>)
-               [ Ssh.onRemote NoConsumeStdin r (\f p -> liftIO (Git.Config.fromPipe r f p Git.Config.ConfigList), return (Left $ giveup "configlist failed")) "configlist" [] []
-               , getConfigViaRsync r gc
+       | not fast = extract <$> getM id
+               [ Ssh.onRemote NoConsumeStdin r (configpipe, return Nothing) "configlist" [] []
+               , getconfig $ getConfigViaRsync r gc
                ]
        | otherwise = return (Nothing, r)
   where
        extract Nothing = (Nothing, r)
        extract (Just r') = (fromConfigValue <$> Git.Config.getMaybe coreGCryptId r', r')
 
-getConfigViaRsync :: Git.Repo -> RemoteGitConfig -> Annex (Either SomeException (Git.Repo, S.ByteString, String))
+       configpipe f p = getconfig $ liftIO $ 
+               Git.Config.fromPipe r f p Git.Config.ConfigList
+
+       getconfig a = do
+               (r', _, exitcode, _) <- a
+               if exitcode == ExitSuccess
+                       then return (Just r')
+                       else return Nothing
+
+getConfigViaRsync :: Git.Repo -> RemoteGitConfig -> Annex (Git.Repo, S.ByteString, ExitCode, String)
 getConfigViaRsync r gc = do
        let (rsynctransport, rsyncurl, _) = rsyncTransport r gc
        opts <- rsynctransport
index d77fce1fd88ed38f89c6a204ac7e09836e8d3a01..2dc132501e95ab9827d810143039938c28caf313 100644 (file)
@@ -278,14 +278,25 @@ tryGitConfigRead autoinit r hasuuid
        | Git.repoIsSsh r = storeUpdatedRemote $ do
                v <- Ssh.onRemote NoConsumeStdin r
                        ( pipedconfig Git.Config.ConfigList autoinit (Git.repoDescribe r)
-                       , return (Left "configlist failed")
+                       , error "internal"
                        )
                        "configlist" [] configlistfields
                case v of
                        Right r'
                                | haveconfig r' -> return r'
-                               | otherwise -> configlist_failed
-                       Left _ -> configlist_failed
+                               | otherwise -> do
+                                       configlist_failed
+                                       return r
+                       Left exitcode -> do
+                               -- ssh exits 255 when there was an error
+                               -- connecting to the remote server.
+                               if exitcode /= ExitFailure 255 
+                                       then do
+                                               configlist_failed
+                                               return r
+                                       else do
+                                               warning $ UnquotedString $ "Unable to connect to repository " ++ Git.repoDescribe r ++ " to get its annex.uuid configuration."
+                                               return r
        | Git.repoIsHttp r = storeUpdatedRemote geturlconfig
        | Git.GCrypt.isEncrypted r = handlegcrypt =<< getConfigMaybe (remoteAnnexConfig r "uuid")
        | Git.repoIsUrl r = do
@@ -298,31 +309,35 @@ tryGitConfigRead autoinit r hasuuid
        haveconfig = not . M.null . Git.config
 
        pipedconfig st mustincludeuuuid configloc cmd params = do
-               v <- liftIO $ Git.Config.fromPipe r cmd params st
-               case v of
-                       Right (r', val, _err) -> do
+               (r', val, exitcode, _err) <- liftIO $ 
+                       Git.Config.fromPipe r cmd params st
+               if exitcode == ExitSuccess
+                       then do
                                unless (isUUIDConfigured r' || val == mempty || not mustincludeuuuid) $ do
                                        warning $ UnquotedString $ "Failed to get annex.uuid configuration of repository " ++ Git.repoDescribe r
                                        warning $ UnquotedString $ "Instead, got: " ++ show val
                                        warning "This is unexpected; please check the network transport!"
                                return $ Right r'
-                       Left l -> do
+                       else do
                                warning $ UnquotedString $ "Unable to parse git config from " ++ configloc
-                               return $ Left (show l)
+                               return $ Left exitcode
 
        geturlconfig = Url.withUrlOptionsPromptingCreds $ \uo -> do
                let url = Git.repoLocation r ++ "/config"
                v <- withTmpFile "git-annex.tmp" $ \tmpfile h -> do
                        liftIO $ hClose h
                        Url.download' nullMeterUpdate Nothing url tmpfile uo >>= \case
-                               Right () -> pipedconfig Git.Config.ConfigNullList
-                                       False url "git"
-                                       [ Param "config"
-                                       , Param "--null"
-                                       , Param "--list"
-                                       , Param "--file"
-                                       , File tmpfile
-                                       ]
+                               Right () ->
+                                       pipedconfig Git.Config.ConfigNullList
+                                               False url "git"
+                                               [ Param "config"
+                                               , Param "--null"
+                                               , Param "--list"
+                                               , Param "--file"
+                                               , File tmpfile
+                                               ] >>= return . \case
+                                                       Right r' -> Right r'
+                                                       Left exitcode -> Left $ "git config exited " ++ show exitcode
                                Left err -> return (Left err)
                case v of
                        Right r' -> do
@@ -342,15 +357,7 @@ tryGitConfigRead autoinit r hasuuid
                                warning $ UnquotedString $ url ++ " " ++ err
                                return r
 
-       {- Is this remote just not available, or does
-        - it not have git-annex-shell?
-        - Find out by trying to fetch from the remote. -}
-       configlist_failed = case Git.remoteName r of
-               Nothing -> return r
-               Just n -> do
-                       whenM (inRepo $ Git.Command.runBool [Param "fetch", Param "--quiet", Param n]) $ do
-                               set_ignore "does not have git-annex installed" True
-                       return r
+       configlist_failed = set_ignore "does not have git-annex installed" True
        
        set_ignore msg longmessage = do
                case Git.remoteName r of
diff --git a/doc/forum/Control_socket_connect__40__..__47__.git__47__annex__47__ssh__47__server.lo/comment_1_f530349d80ca5f43e9b2304f63e4b960._comment b/doc/forum/Control_socket_connect__40__..__47__.git__47__annex__47__ssh__47__server.lo/comment_1_f530349d80ca5f43e9b2304f63e4b960._comment
new file mode 100644 (file)
index 0000000..f3b36a3
--- /dev/null
@@ -0,0 +1,16 @@
+[[!comment format=mdwn
+ username="joey"
+ subject="""comment 1"""
+ date="2025-01-03T17:33:05Z"
+ content="""
+I don't know why ssh is failing to connect via the socket file like that.
+Generally when a socket file like this exists, there is a background ssh
+process that is servicing connections to it. It may be there was some
+problem with that process.
+
+git-annex interprets ssh failing as the ssh server not having git-annex
+installed, even though in this case it's some other problem. I have made
+some changes that improve this, so in this situation it should not set
+annex-ignore again, but will instead complain that it cannot connect to the
+server.
+"""]]